package yadong.example.code;

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;


/**
 * 
 * perform AES encryption / decryption
 * @author lees
 *
 */
public class Crypto {
	private final static long MAX_TOKEN_LIFE_IN_MILLIS = 300000; // 5 mins
	private final static String AES = "AES";
	
	private String aesKey;
	
	/**
	 * initialize encoder/decoder
	 */
	public Crypto() {
		this.aesKey = "9e98b34fdb7824b1848764e1ea0f21c1";
	}
	
	/**
	 * initialize encoder/decoder with the supplied aeskey
	 * @param aesKey
	 */
	public Crypto(String aesKey) {
		this.aesKey = aesKey;
	}

	/**
	 * convert buffer to hex string
	 * @param buf
	 * @return hex string
	 */
	public static String toHexString(byte buf[]) {
		StringBuffer strbuf = new StringBuffer(buf.length * 2);
		int i;
		for (i = 0; i < buf.length; i++) {
			if (((int) buf[i] & 0xff) < 0x10)
				strbuf.append("0");
			strbuf.append(Long.toString((int) buf[i] & 0xff, 16));
		}
		return strbuf.toString();
	}
	
	/**
	 * convert hex string to buffer
	 * @param hexString
	 * @return byte array
	 */
	public static byte[] parseHexString(String hexString) {
		if ((hexString.length() % 2) == 0) {
			byte[] result = new byte[hexString.length() / 2];
			for (int i=0; i<hexString.length(); i=i+2) {
				result[i/2] = Integer.decode("#"+hexString.substring(i, i+2)).byteValue();
			}
			return result;
		} else {
			return null;
		}
	}
	
	/**
	 * generate encryption key
	 * @return encryption key in byte array
	 */
	public static byte[] generateKey() {
		byte[] result = null;
		try {
			KeyGenerator kgen = KeyGenerator.getInstance(AES);
			kgen.init(128);
			SecretKey skey = kgen.generateKey();
			result = skey.getEncoded();
		} catch (NoSuchAlgorithmException e) {
		}
		return result;
	}
	
	/**
	 * encrypt a string using specified key
	 * @param plainText
	 * @param key
	 * @return encrypted message in byte array
	 */
	private byte[] encrypt(String plainText, byte[] key) {
		byte[] result = null;
		try {
			SecretKeySpec keySpec = new SecretKeySpec(key, AES);
			Cipher cipher = Cipher.getInstance(AES);
			cipher.init(Cipher.ENCRYPT_MODE, keySpec);
			result = cipher.doFinal(plainText.getBytes());
		} catch (InvalidKeyException e) {
		} catch (NoSuchAlgorithmException e) {
		} catch (NoSuchPaddingException e) {
		} catch (IllegalBlockSizeException e) {
		} catch (BadPaddingException e) {
		}
		return result;
	}

	/**
	 * decrypt using specified key
	 * @param encrypted
	 * @param key
	 * @return decrypted message
	 */
	private String decrypt(byte[] encrypted, byte[] key) {
		byte[] original = null;
		try {
			SecretKeySpec keySpec = new SecretKeySpec(key, AES);
			Cipher cipher = Cipher.getInstance(AES);
			cipher.init(Cipher.DECRYPT_MODE, keySpec);
			original = cipher.doFinal(encrypted);
		} catch (InvalidKeyException e) {
		} catch (NoSuchAlgorithmException e) {
		} catch (NoSuchPaddingException e) {
		} catch (IllegalBlockSizeException e) {
		} catch (BadPaddingException e) {
		}
		return (original == null) ? null : new String(original);
	}
		
	/**
	 * construct a security token
	 * @return security token string
	 */
	public String getToken() {
		byte[] aesKey = parseHexString(this.getAesKey());
		String time = Long.toHexString(System.currentTimeMillis());
		byte[] encrypted = encrypt(time, aesKey);
		return (encrypted == null) ? null : toHexString(encrypted).toUpperCase();
	}
	
	/**
	 * encrypt a string
	 * @return encrypted string
	 */
	public String getEncryptedString(String myString) {
		byte[] aesKey = parseHexString(this.getAesKey());
		byte[] encrypted = encrypt(myString, aesKey);
		return (encrypted == null) ? null : toHexString(encrypted).toUpperCase();
	}
	
	/**
	 * Decrypt a token
	 * @param token 
	 * @return Decrypted String
	 */
	public String getDecryptedString(String token) {
		byte[] aesKey = parseHexString(this.getAesKey());
		return decrypt(parseHexString(token), aesKey);
	}
	
	/**
	 * check whether a token is valid
	 * @param token 
	 * @return boolean
	 */
	public boolean isValidToken(String token) {
		byte[] aesKey = parseHexString(this.getAesKey());
		String original = decrypt(parseHexString(token), aesKey);
		if (original != null) {
			long tokenTime = Long.parseLong(original, 16);		
			return Math.abs(System.currentTimeMillis() - tokenTime) < MAX_TOKEN_LIFE_IN_MILLIS;
		} else {
			return false;
		}
	}

	public String getAesKey() {
		return aesKey;
	}

	public void setAesKey(String aesKey) {
		this.aesKey = aesKey;
	}
}
